/**
 * 高亮指定的元素。
 * 首先移除所有已经高亮的元素，然后根据提供的id参数来高亮对应的元素。
 * 如果提供的id是一个HTMLElement实例，则直接给这个元素添加高亮类。
 * 如果提供的id是一个以'#'开头的字符串，则认为是id选择器，去掉了'#'后通过id来查找并高亮元素。
 * 如果提供的id不是HTMLElement实例且不以'#'开头，将尝试通过a标签的href属性值来查找并高亮元素。
 *
 * @param {string|HTMLElement} id - 要高亮的元素的id或者直接是一个HTMLElement实例。
 */
function highlight(id) {
  // 移除所有当前高亮的元素
  document
    .querySelectorAll("a.highlight")
    .forEach((a) => a.classList.remove("highlight"))

  // 如果id是HTMLElement实例，则直接给这个元素添加高亮类
  if (id instanceof HTMLElement) {
    id.classList.add("highlight")
    return
  }

  // 如果id以'#'开头，去掉'#'后处理
  if (id.startsWith("#")) {
    id = id.substring(1)
  }

  // 通过id或者a标签的href属性值来查找并添加高亮类
  document.querySelector(`a[href="#${id}"]`).classList.add("highlight")
}

/**
 * 函数防抖动封装。
 * 防抖动函数确保一个函数不会在短时间内被频繁调用，通常用于优化性能。
 * @param {Function} fn 要执行的函数。
 * @param {number} delay 延迟的毫秒数，默认为100。
 * @returns {Function} 返回一个新的函数，该函数具有防抖功能。
 */
function debounce(fn, delay = 100) {
  // 用于存储定时器的变量
  let timer = null

  // 返回一个包装函数
  return function (...args) {
    // 清除之前的定时器
    clearTimeout(timer)
    // 设置新的定时器，并在延迟后执行原函数
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}

/**
 * 初始化页面中的链接点击事件，并收集所有目标元素的标题。
 * 该代码块不接受参数，也不直接返回值，但会填充 `titles` 数组。
 */
const links = document.querySelectorAll('a[href^="#"]') // 获取所有以 "#" 开头的链接元素
const titles = []
for (const link of links) {
  // 为每个链接添加点击事件监听器
  link.addEventListener("click", () => {
    highlight(link) // 点击时高亮链接
  })
  const url = new URL(link.href) // 解析链接的 URL
  const dom = document.querySelector(url.hash) // 根据链接的 hash 值寻找对应元素
  if (dom) {
    titles.push(dom) // 如果找到元素，将其添加到 titles 数组中
  }
}

/**
 * 使用防抖技术处理滚动事件的函数。
 * 该函数会根据滚动位置，高亮显示最接近视口顶部的标题。
 *
 * @returns {Function} 返回一个经过防抖处理的回调函数。
 */
const scrollHanlder = debounce(() => {
  // 获取所有标题的边界矩形
  const rects = titles.map((title) => title.getBoundingClientRect())
  const range = 300 // 定义一个范围值，用于判断标题是否在可视区域内

  // 遍历每个标题，检查其位置以决定是否需要高亮
  for (let i = 0; i < rects.length; i++) {
    const title = titles[i]
    const rect = rects[i]

    // 如果标题在视口顶部范围内，则高亮该标题，并结束循环
    if (rect.top >= 0 && rect.top <= range) {
      highlight(title.id)
      break
    }

    // 如果标题已经离开视口顶部，但下一个标题仍在视口底部，则高亮当前标题，并结束循环
    if (
      rect.top < 0 &&
      rects[i + 1] &&
      rects[i + 1].top > document.documentElement.clientHeight
    ) {
      highlight(titles[i].id)
      break
    }
  }
}, 100) // 防抖延迟设置为100毫秒

window.addEventListener("scroll", scrollHanlder)
